// TYLunarCalendar
// 
// Copyright (c) 2017 wty21cn <wty21cn@gmail.com>
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

public struct TYLunarDateComponent {

    public var solarYear: Int?

    public var lunarYear: LunarYear?

    public var solarMonth: Int?

    public var lunarMonth: LunarMonth?

    public var solarDay: Int?

    public var weekDay: Int?

    public var zodiac: Zodiac?

    public var solarTerm: SolarTerm?

    public var lunarFestival: LunarFestival?

    public var isLeapMonth: Bool?

}

extension TYLunarDateComponent {
    /// Heavenly stems order 甲:1 乙:2 丙:3 丁:4 戊:5 己:6 庚:7 辛:8 壬:9 癸:10
    /// Earthly Branches order 寅:1 卯:2 辰:3 巳:4 午:5 未:6 申:7 酉:8 戌:9 亥:10 子:11 丑:12
    public static func calculateLunarYearOrdinal(fromSolarYear solarYear: Int) -> Int {
        var heavenlyStem = (solarYear % 10 + 7) % 10
        var earthlyBranch = (solarYear + 7) % 12
        return (6 * heavenlyStem - 5 * earthlyBranch + 60) % 60
    }
}

//  Heavenly stems and Earthly Branches Table
//  01 甲子 11 甲戌 21 甲申 31 甲午 41 甲辰 51 甲寅
//  02 乙丑 12 乙亥 22 乙酉 32 乙未 42 乙巳 52 乙卯
//  03 丙寅 13 丙子 23 丙戌 33 丙申 43 丙午 53 丙辰
//  04 丁卯 14 丁丑 24 丁亥 34 丁酉 44 丁未 54 丁巳
//  05 戊辰 15 戊寅 25 戊子 35 戊戌 45 戊申 55 戊午
//  06 己巳 16 己卯 26 己丑 36 己亥 46 己酉 56 己未
//  07 庚午 17 庚辰 27 庚寅 37 庚子 47 庚戌 57 庚申
//  08 辛未 18 辛巳 28 辛卯 38 辛丑 48 辛亥 58 辛酉
//  09 壬申 19 壬午 29 壬辰 39 壬寅 49 壬子 59 壬戌
//  10 癸酉 20 癸未 30 癸巳 40 癸卯 50 癸丑 60 癸亥

// Chinese Zodiac
// 子鼠, 丑牛, 寅虎, 卯兔, 辰龙, 巳蛇, 午马, 未羊, 申猴, 酉鸡, 戌狗, 亥猪

extension TYLunarDateComponent {

    public struct Zodiac: CustomStringConvertible {

        public let zodiacOrdinal: Int

        public init(lunarYearOrdinal: Int) {
            precondition(1 <= lunarYearOrdinal && lunarYearOrdinal <= 60, "Invalid lunar year ordinal \(lunarYearOrdinal)!")
            zodiacOrdinal = lunarYearOrdinal % 12
        }

        public init(solarYear: Int) {
            self.init(lunarYearOrdinal: TYLunarDateComponent.calculateLunarYearOrdinal(fromSolarYear: solarYear))
        }

        public init(zodiacOrdinal: Int) {
            precondition(0 <= zodiacOrdinal && zodiacOrdinal <= 11, "Invalid zodiac ordinal \(zodiacOrdinal)!")
            self.zodiacOrdinal = zodiacOrdinal
        }

        // The description order is calculated according to Heavenly stems and Earthly Branches Table
        static let symbols = [
            1: "鼠", 2: "牛", 3: "虎", 4: "兔", 5: "龙", 6:"蛇",
            7: "马", 8: "羊", 9: "猴", 10: "鸡", 11: "狗", 0: "猪"
        ]
        public var description: String { return Zodiac.symbols[zodiacOrdinal]! }
    }

    public struct LunarYear: CustomStringConvertible {

        public let solarYear: Int
        public let lunarYearOrdinal: Int

        public init(solarYear: Int) {
            self.lunarYearOrdinal = TYLunarDateComponent.calculateLunarYearOrdinal(fromSolarYear: solarYear)
            self.solarYear = solarYear
        }

        // The description order is calculated according to Heavenly stems and Earthly Branches Table
        static let symbols = [
            1: "甲子", 11: "甲戌", 21: "甲申", 31: "甲午", 41: "甲辰", 51: "甲寅",
            2: "乙丑", 12: "乙亥", 22: "乙酉", 32: "乙未", 42: "乙巳", 52: "乙卯",
            3: "丙寅", 13: "丙子", 23: "丙戌", 33: "丙申", 43: "丙午", 53: "丙辰",
            4: "丁卯", 14: "丁丑", 24: "丁亥", 34: "丁酉", 44: "丁未", 54: "丁巳",
            5: "戊辰", 15: "戊寅", 25: "戊子", 35: "戊戌", 45: "戊申", 55: "戊午",
            6: "己巳", 16: "己卯", 26: "己丑", 36: "己亥", 46: "己酉", 56: "己未",
            7: "庚午", 17: "庚辰", 27: "庚寅", 37: "庚子", 47: "庚戌", 57: "庚申",
            8: "辛未", 18: "辛巳", 28: "辛卯", 38: "辛丑", 48: "辛亥", 58: "辛酉",
            9: "壬申", 19: "壬午", 29: "壬辰", 39: "壬寅", 49: "壬子", 59: "壬戌",
            10: "癸酉", 20: "癸未", 30: "癸巳", 40: "癸卯", 50: "癸丑", 60: "癸亥"
        ]
        public var description: String { return LunarYear.symbols[lunarYearOrdinal]! }
    }

    public struct LunarMonth: CustomStringConvertible {

        public let lunarMonthOrdinal: Int

        public init(lunarMonthOrdinal: Int) {
            precondition(1 <= lunarMonthOrdinal && lunarMonthOrdinal <= 12, "Invalid lunar month ordinal \(lunarMonthOrdinal)!")
            self.lunarMonthOrdinal = lunarMonthOrdinal
        }

        static let symbols = [
            1:"正", 2:"二", 3:"三", 4:"四", 5:"五", 6 :"六", 7 :"七", 8:"八", 9:"九", 10:"十", 11:"冬", 12:"腊"
        ]
        public var description: String { return LunarMonth.symbols[lunarMonthOrdinal]! }
    }

    public struct LunarDay: CustomStringConvertible {

        public let lunarDayOrdinal: Int

        public init(lunarDayOrdinal: Int) {
            precondition(1 <= lunarDayOrdinal && lunarDayOrdinal <= 30, "Invalid lunar day ordinal \(lunarDayOrdinal)!")
            self.lunarDayOrdinal = lunarDayOrdinal
        }
        static let symbols = [
            1: "初一", 2: "初二", 3: "初三", 4: "初四", 5: "初五", 6: "初六", 7: "初七", 8: "初八", 9: "初九", 10: "初十",
            11: "十一", 12: "十二", 13: "十三", 14: "十四", 15: "十五", 16: "十六", 17: "十七", 18: "十八", 19: "十九", 20: "二十",
            21: "廿一", 22: "廿二", 23: "廿三", 24: "廿四", 25: "廿五", 26: "廿六", 27: "廿七", 28: "廿八", 29: "廿九", 30: "三十"
        ]
        public var description: String { return LunarDay.symbols[lunarDayOrdinal]! }
    }

    public struct LunarFestival: CustomStringConvertible {

        public let lunarMonthOrdinal: Int

        public let lunarDayOrdinal: Int

        public init(lunarMonthOrdinal: Int, lunarDayOrdinal: Int) {
            precondition(1 <= lunarDayOrdinal && lunarDayOrdinal <= 30, "Invalid lunar day ordinal \(lunarDayOrdinal)!")
            precondition(1 <= lunarMonthOrdinal && lunarMonthOrdinal <= 12, "Invalid lunar month ordinal \(lunarMonthOrdinal)!")
            self.lunarMonthOrdinal = lunarMonthOrdinal
            self.lunarDayOrdinal = lunarDayOrdinal
        }

        static let symbols = ["204": "abc"]

        public var description: String { return LunarFestival.symbols["\(lunarMonthOrdinal)\(lunarDayOrdinal)"] }
    }

    public struct SolarTerm: CustomStringConvertible {

        public let minSolarYearForTerm = 1890
        public let maxSolarYearForTerm = 2100

        public let solarYear: Int
        public let solarMonth: Int
        public let solarDay: Int
        public let term: String

        public init?(solarYear: Int, solarMonth: Int, solarDay: Int) {
            guard let termMapForYear = SolarTerm.termMap[solarYear] else { return nil }
            guard let symbolIdx = termMapForYear["\(solarMonth)\(solarDay)"] else { return nil }
            self.solarYear = solarYear
            self.solarMonth = solarMonth
            self.solarDay = solarDay
            term = SolarTerm.symbols[symbolIdx]!
        }

        static let symbols = [
            1: "立春", 2: "雨水", 3: "惊蛰", 4: "春分", 5: "清明", 6: "谷雨", 7: "立夏", 8: "小满", 9: "芒种", 10: "夏至", 11: "小暑", 12: "大暑",
            13: "立秋", 14: "处暑", 15: "白露", 16: "秋分", 17: "寒露", 18: "霜降", 19: "立冬", 20: "小雪", 21: "大雪", 22: "冬至", 23: "小寒", 24: "大寒"
        ]
        public var description: String { return term }

        static let termMap = [
            1890: ["204": 1 ]
        ]
    }
}
